package org.vaadin.nf4v.core.config.builder;

import org.vaadin.nf4v.core.config.*;
import org.vaadin.nf4v.core.config.process.NavigationConfigProcessor;
import org.vaadin.nf4v.core.handler.*;
import org.vaadin.nf4v.core.handler.WindowFactory;

import java.util.ArrayList;
import java.util.List;

/**
 * Navigation configuration builder (DSL).
 * Extend your from this class and implement configure().
 */
public abstract class NavigationConfigBuilder extends NavigationConfigProcessor {

    /**
     * Shortcut method to register default handler.
     *
     * @param handler handler instance
     * @throws ContextConfigurationException if handler didn't implement handler interface or if
     *                                       handler is already registered for this configuration.
     */
    protected <T extends Handler> void registerHandler(T handler) throws ContextConfigurationException {
        registerHandler(getNavigationConfig(), handler);
    }

    /**
     * Registers window uri handler.
     * Uri handler is responsible for page name uri fragment detection,
     * e.g. site.com/ui#uristring/params -> uristring
     * If not defined DefaultUriHandler will be used (which threats uri as string before first "/").
     *
     * @param uriHandler uri handler instance
     */
    protected void uriHandler(UriHandler uriHandler) {
        registerHandler(uriHandler);
    }

    /**
     * Registers invalid uri handler.
     * Invalid uri handler is used to handle situations when uri string could not be parsed (either page not found
     * or parameters string is incorrect)
     * If not defined DefaultInvalidUriHandler will be used.
     *
     * @param invalidUriHandler invalid uri handler instance
     */
    protected void invalidUriHandler(InvalidUriHandler invalidUriHandler) {
        registerHandler(invalidUriHandler);
    }

    /**
     * Registers navigation uncaught error handler.
     * Navigation uncaught error handler is responsible for handling uncaught errors during uri parsing or NavigationTarget
     * analysing phase.
     * If not defined DefaultNavigationUncaughtErrorHandler will be used.
     *
     * @param navigationUncaughtErrorHandler navigation uncaught error handler instance
     */
    protected void navigationUncaughtErrorHandler(NavigationUncaughtErrorHandler navigationUncaughtErrorHandler) {
        registerHandler(navigationUncaughtErrorHandler);
    }

    /**
     * Registers home page resolver.
     * Home page resolver is responsible for resolving home page for context.
     * If not defined DefaultHomePageResolver will be used.
     *
     * @param homePageResolver home page resolver instance
     */
    protected void homePageResolver(HomePageResolver homePageResolver) {
        registerHandler(homePageResolver);
    }

    /**
     * Registers default parameter resolver.
     * Used to parse incoming parameter from uri string.
     * If default not defined DefaultParamResolver will be used.
     *
     * @param paramResolver default param resolver instance
     */
    protected void defaultParamResolver(ParamResolver paramResolver) {
        registerHandler(paramResolver);
    }

    /**
     * Registers window factory used to instantiate windows.
     * If not defined DefaultWindowFactory will be used.
     *
     * @param windowFactory window factory instance
     */
    protected void windowFactory(WindowFactory windowFactory) {
        registerHandler(windowFactory);
    }

    /**
     * Registers page factory used to instantiate pages.
     * If not defined DefaultPageFactory will be used.
     *
     * @param pageFactory page factory instance
     */
    protected void pageFactory(PageFactory pageFactory) {
        registerHandler(pageFactory);
    }

    /**
     * Registers new context.
     * Context is a window, which support navigation inside it.
     * Contexts are needed to create separate site areas.
     * Example of using contexts:
     * * "main" for public area: handled by window on url yoursite.com/
     * * "admin" for administration back-end: handled by window on url yoursite.com/admin
     *
     * @param context name of context
     * @return context configuration builder
     */
    protected ContextConfigBuilder context(String context) {
        if (context==null || "".equals(context.trim())){
            throw new ContextConfigurationException("Context name can't be null");
        }
        if (getNavigationConfig().isContextExists(context)) {
            throw new ContextConfigurationException("Context '" + context + "' already registered");
        }
        ContextConfig contextConfig = new ContextConfig(context.trim());
        getNavigationConfig().addContextConfig(contextConfig);
        return new ContextConfigBuilder(contextConfig, this);
    }

    /**
     * Allows to register pages shared between few contexts
     *
     * @param contexts affected context names
     * @return context pages builder
     */
    protected ContextPagesConfigBuilder inContexts(String... contexts) {
        if (contexts.length == 0) {
            throw new ContextConfigurationException("Context names are required for pages specification");
        }
        List<ContextConfig> configs = new ArrayList<ContextConfig>();
        for (String context : contexts) {
            if (context == null || "".equals(context.trim())) {
                throw new ContextConfigurationException("Context name can't be empty");
            }
            if (!getNavigationConfig().isContextExists(context)) {
                throw new ContextConfigurationException("Context '" + context + "' is not registered. Use context(name) method to register context");
            }
            configs.add(getNavigationConfig().getContextConfig(context));
        }
        return new ContextPagesConfigBuilder(configs, this);
    }

    /**
     * Allows to register pages specific to current context
     *
     * @param context context name
     * @return context pages configuration builder
     */
    protected ContextPagesConfigBuilder inContext(String context) {
        if (context == null) {
            throw new ContextConfigurationException("Context name is required for pages specification");
        }
        return inContexts(context);
    }

    /**
     * Allows to register pages in all registered contexts.
     * Note: if new context will be registered below this statement it will not contain this pages!
     *
     * @return context pages configuration builder
     */
    protected ContextPagesConfigBuilder inAllContexts() {
        List<ContextConfig> configs = getNavigationConfig().getContextConfigs();
        String[] configsarr = new String[configs.size()];
        int i=0;
        for(ContextConfig config: configs){
            configsarr[i++]=config.getName();
        }
        return inContexts(configsarr);
    }

    /**
     * Allows to register navigation parameter.
     *
     * @param name  parameter name
     * @param value parameter value
     */
    protected void param(String name, String value) {
        getNavigationConfig().setParam(name, value);
    }
}
